package com.apes.framework.util;

import com.alibaba.fastjson.JSONObject;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import lombok.Data;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component
public class Deadlock {

    @Autowired
    private EntityManager em;

    public void kill(SimpleRequest request) {
        JSONObject jo = request.getJO();
        int secondsInWait = 120;
        if (jo.containsKey("secondsInWait")) {
            secondsInWait = jo.getIntValue("secondsInWait");
        }
        List<Session> sessions = new ArrayList<>();
        em.createNativeQuery(
                "select sid, serial, lev, status, seconds_in_wait, blocking_session, exec_sql\n" +
                        "  from (select s.username, level as lev, s.sid, s.serial# as serial, s.status,\n" +
                        "                s.seconds_in_wait, s.blocking_session,\n" +
                        "                'alter system kill session ''' || s.sid || ',' || s.serial# || '''' exec_sql\n" +
                        "           from v$session s\n" +
                        "          where s.blocking_session is not null\n" +
                        "             or s.sid in (select distinct blocking_session from v$session)\n" +
                        "          start with s.blocking_session is null\n" +
                        "         connect by prior s.sid = s.blocking_session) a\n" +
                        " where a.lev <= 2"
        ).unwrap(NativeQueryImpl.class)
                .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
                .getResultStream()
                .forEach(m -> {
                    Map row = ((Map) m);
                    if (((BigDecimal) row.get("LEV")).intValue() == 1) {
                        if (row.get("STATUS").equals("INACTIVE")) {
                            Session session = sessions.stream().filter(o -> o.getSid() == ((BigDecimal) row.get("SID")).intValue()).findAny().orElse(null);
                            if (session == null) {
                                session = new Session(((BigDecimal) row.get("SID")).intValue(), ((BigDecimal) row.get("SECONDS_IN_WAIT")).intValue(), (String) row.get("EXEC_SQL"));
                                sessions.add(session);
                            } else {
                                session.setExecSql((String) row.get("EXEC_SQL"));
                            }
                        }
                    } else {
                        if (row.get("STATUS").equals("ACTIVE")) {
                            Session session = sessions.stream().filter(o -> o.getSid() == ((BigDecimal) row.get("BLOCKING_SESSION")).intValue()).findAny().orElse(null);
                            int inWait = ((BigDecimal) row.get("SECONDS_IN_WAIT")).intValue();
                            if (session != null) {
                                if (session.getSecondsInWait() < inWait) {
                                    session.setSecondsInWait(inWait);
                                }
                            } else {
                                session = new Session(((BigDecimal) row.get("BLOCKING_SESSION")).intValue(), ((BigDecimal) row.get("SECONDS_IN_WAIT")).intValue(), null);
                                sessions.add(session);
                            }
                        }
                    }
                });
        int finalSecondsInWait = secondsInWait;
        sessions.stream().filter(session -> session.getSecondsInWait() >= finalSecondsInWait && session.getExecSql() != null)
                .forEach(session -> em.createNativeQuery(session.getExecSql()).executeUpdate());
    }

    @Data
    class Session {
        private int sid;
        private int secondsInWait;
        private String execSql;

        public Session(int sid, int secondsInWait, String execSql) {
            this.sid = sid;
            this.secondsInWait = secondsInWait;
            this.execSql = execSql;
        }
    }
}
